#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "channel.h"
#include <algorithm>
#include <iostream>
#include <stdio.h>
#define SUM_LEVEL 256

Channel::Channel()
    :Mat()
{
    color = 0;
}

Channel::Channel(cv::Mat image, int c)
    :Mat(image)
{
    color = c;
}

Channel::Channel(Channel& channel)
    :Mat((Mat&)channel)
{
    color = channel.color;
}

Channel& Channel::operator=(const Channel& channel)
{
    Mat::operator=(channel);
    color = channel.color;
    return *this;
}

Channel::~Channel()
{
}

cv::Scalar getScalar(Channel channel)
{
    switch(channel.color)
    {
	case 0:
	    return cv::Scalar(255, 0, 0);
	case 1:
	    return cv::Scalar(0, 255, 0);
	case 2:
	    return cv::Scalar(0, 0, 255);
    }
    return  cv::Scalar(255, 0, 0);
}  

template <typename T>
T getMAX(T *data, int N)
{
    T max = 0;
    for (int i = 0; i < N; i++)
    {
	if(max < data[i])
	    max = data[i];
    }
    return max;
}

template <typename T>
void initHistData(T *data, int N)
{
    for (int i = 0; i < N; i++)
	data[i] = 0;
}

//Use Y-cut for construct the histogram
template <typename T>
void Channel::setHistData(T *data, int N)
{
    int img_w = this->cols;
    for(int i = 0; i < N; i++)
    {
	for (int j = 0; j < img_w; ++j)
	{
	    if(this->at<uchar>(i, j) == 0)
		data[i]++;
	}
    }
}

/*
  After construct the data of histogram, we draw the histogram to image
 */
void Channel::drawHistogram(cv::Mat &hist_background, int color)
{
    int N = hist_background.rows;
    int *data = new int[N];
    cv::Scalar scalar(color);
    cv::Point point_start, point_end;
    initHistData(data, N);
    setHistData(data, N);
    for (int i = 0; i < N; i++)
    {
	point_start = cv::Point(0, i);
	point_end = cv::Point(data[i], i);
	cv::line( hist_background, point_start, point_end, scalar, 1, 8, 0 );
    }
    delete []data;
}

//Compare 2 reals with error e
bool Channel::compare(double a, double b, double e)
{
    double d = a - b;
    return(d < e && d > -e);
}

/*
  Compare 2 image with:
  -Number of block
  -the tail between 2 blocks correspondent
  -The different between 2 first blocks of 2 images with error
 */
bool Channel::compare(cv::Mat type1, cv::Mat type2,
		      std::vector<Block> v1, std::vector<Block> v2, double e)
{
    double seuil_score = 0.9;
    int N1 = v1.size();
    int N2 = v2.size();
    if(N1 != N2)
	return false;
    for (int i = 0; i < N1; ++i)
    {
	if(!compare(v1[i].height, v2[i].height, e))
	    return false;
    }
    cv::Mat block0_type1;
    cv::Mat block0_type2;
    detectBlock(type1, 0, v1[0].height, block0_type1);
    detectBlock(type2, 0, v2[0].height, block0_type2);
    double score = Channel::score(block0_type1, block0_type2);
    return (score > seuil_score);
}

/*
  Calcul the score correct between 2 images
 */
double Channel::score(cv::Mat img1, cv::Mat img2)
{
    long nb_commun = 0;
    int nb_pixel = 0;
    cv::resize(img2, img2, img1.size());
    for (int i = 0; i < img1.rows; ++i)
    {
	for (int j = 0; j < img1.cols; ++j)
	{
	    nb_pixel++;
	    if(img1.at<uchar>(i, j) == img2.at<uchar>(i, j))
		nb_commun++;
	}
    }
    return nb_commun/(double)nb_pixel;
}

//Calcul the number different between 2 images
double Channel::different(cv::Mat img1, cv::Mat img2)
{
    long nb_diff = 0;
    cv::resize(img2, img2, img1.size());
    for (int i = 0; i < img1.rows; ++i)
    {
	for (int j = 0; j < img1.cols; ++j)
	{
	    if(img1.at<uchar>(i, j) != img2.at<uchar>(i, j))
		nb_diff++;
	}
    }
    return (double)nb_diff/(double)(img1.rows*img1.cols);
}

double Channel::diffBlock(cv::Mat img1, cv::Mat img2)
{
    long N1 = cv::sum(img1)[0];
    long N2 = cv::sum(img2)[0];
    return (N1 - N2);
}

/*
  THis function is used to detect blocks of the method Y-cut
 */
void Channel::detectBlock(std::vector<Block> &blocks, bool space)
{
    int N = this->rows;
    int *data = new int[N];
    cv::Scalar scalar(0);
    cv::Point point_start, point_end;
    initHistData(data, N);
    setHistData(data, N);
    int max = getMAX(data, N);
    int k = 0;
    int position_prev = 0;
    Block block;
    for (int i = 0; i < N-1; ++i)
    {
	if(!space)
	{
	    if(compare(data[i], max, 50) && !compare(data[i+1], max, 50))
	    {
		block.id = k++;
		block.height = i - position_prev;
		block.position = position_prev;
		blocks.push_back(block);
		position_prev = i;
	    }
	}
	else{
	    if(data[i] != 0)
	    {
		block.id = k++;
		int j = i;
		while(data[j] != 0)
		{
		    j++;
		}
		block.height = j - i;
		block.position = i;
		blocks.push_back(block);
		i = j-1;
	    }
	}
    }
    delete []data;
    if(!space)
    {
	block.id = k;
	block.height = N-1 - position_prev;
	block.position = position_prev;
	blocks.push_back(block);
    }
}

//Leave one part of image
void Channel::detectBlock(cv::Mat image, int start, int end, cv::Mat &block)
{
    cv::Rect rectangle(0, start, image.cols, end);
    block = image(rectangle).clone();
}

//Detect block by divide the image to nb_block^2 parts
void Channel::detectBlock(std::vector<cv::Mat> &blocks, int nb_block)
{
    double M = this->rows;
    double N = this->cols;
    double segment_m = M/(double)nb_block;
    double segment_n = N/(double)nb_block;
    cv::Mat image(segment_m, segment_n, CV_8UC1);
    for (int i = 0; i < nb_block; ++i)
    {
	for (int j = 0; j < nb_block; ++j)
	{
	    cv::Rect rectangle(j*segment_n, i*segment_m,
			       segment_n, segment_m);
	    cv::rectangle(*this, cv::Point(j*segment_n, i*segment_m),
			  cv::Point((j+1)*segment_n, (i+1)*segment_m),
			  0, 2);
	    cv::Mat image;
	    (*this)(rectangle).copyTo(image);
	    blocks.push_back(image);
	}
    }
}

//Preprocessing image
cv::Mat Channel::preprocess(cv::Mat image, int option)
{
    cv::Mat image_temp;
    if(image.channels() > 1)
	cv::cvtColor(image, image_temp, CV_RGB2GRAY);
    cv::threshold(image_temp, image_temp, 250, 255,
		  CV_THRESH_BINARY | CV_THRESH_OTSU);
    int M = image_temp.rows;
    int N = image_temp.cols;
    for (int i = 0; i < M; ++i)
    {
	for (int j = 0; j < N; ++j)
	{
	    if(Channel::nearBoard(i, j, image_temp))
	    {
		image_temp.at<uchar>(i, j) = 255;
	    }
	}
    }
    if(option == 1)
    {
	cv::resize(image_temp, image_temp,
		   cv::Size(image_temp.cols/5, image_temp.rows/5));
	cv::blur(image_temp, image_temp, cv::Size(3, 3));
	cv::threshold(image_temp, image_temp, 250, 255,
		      CV_THRESH_BINARY | CV_THRESH_OTSU);
    }
    return image_temp;
}

/*
  To check the condition near board of image
 */
bool Channel::nearBoard(int i, int j, cv::Mat image)
{
    int seuil = 20;
    return (i < seuil || i > image.rows - seuil||
	    j < seuil || j > image.cols - seuil);
}
